﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace CodeLibrary.Controls.Buttons.GlassButton
{
    /// <summary>
    /// Represents a glass button control.
    /// </summary>
    [ToolboxBitmap(typeof(GlassButton)), ToolboxItem(true), ToolboxItemFilter("System.Windows.Forms"), 
    Description("Raises an event when the user clicks it.")]
    public partial class GlassButton : Button
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GlassButton" /> class.
        /// </summary>
        public GlassButton()
        {
            InitializeComponent();
            SetDefaultProperties();
            InitSkins();
        }
        
        /// <summary>
        /// Gets or sets the background color of the control.
        /// </summary>
        /// <returns>A <see cref="T:System.Drawing.Color" /> value representing the background color.</returns>
        [DefaultValue(typeof(Color), "Black")]
        public virtual new Color BackColor
        {
            get { return _backColor; }
            set
            {
                if (_backColor.Equals(value)) return;
                _backColor = value;
                OnBackColorChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the foreground color of the control.
        /// </summary>
        /// <returns>The foreground <see cref="T:System.Drawing.Color" /> of the control.</returns>
        [DefaultValue(typeof(Color), "White")]
        public virtual new Color ForeColor
        {
            get { return base.ForeColor; }
            set { base.ForeColor = value; }
        }
        
        /// <summary>
        /// Gets or sets the inner border color of the control.
        /// </summary>
        /// <returns>A <see cref="T:System.Drawing.Color" /> value representing the color of the inner border.</returns>
        [DefaultValue(typeof(Color), "Black"), Category("Appearance"), 
        Description("The inner border color of the control.")]
        public virtual Color InnerBorderColor
        {
            get { return _innerBorderColor; }
            set
            {
                if (_innerBorderColor == value) return;
                _innerBorderColor = value;
                OnInnerBorderColorChanged(EventArgs.Empty);
            }
        }
        
        /// <summary>
        /// Gets or sets the outer border color of the control.
        /// </summary>
        /// <returns>A <see cref="T:System.Drawing.Color" /> value representing the color of the outer border.</returns>
        [DefaultValue(typeof(Color), "White"), Category("Appearance"), 
        Description("The outer border color of the control.")]
        public virtual Color OuterBorderColor
        {
            get { return _outerBorderColor; }
            set
            {
                if (_outerBorderColor == value) return;
                _outerBorderColor = value;
                OnOuterBorderColorChanged(EventArgs.Empty);
            }
        }
        
        /// <summary>
        /// Gets or sets the shine color of the control.
        /// </summary>
        /// <returns>A <see cref="T:System.Drawing.Color" /> value representing the shine color.</returns>
        [DefaultValue(typeof(Color), "White"), Category("Appearance"), 
        Description("The shine color of the control.")]
        public virtual Color ShineColor
        {
            get { return _shineColor; }
            set
            {
                if (_shineColor == value) return;
                _shineColor = value;
                OnShineColorChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the glow color of the control.
        /// </summary>
        /// <returns>A <see cref="T:System.Drawing.Color" /> value representing the glow color.</returns>
        [DefaultValue(typeof(Color), "255,141,189,255"), Category("Appearance"), 
        Description("The glow color of the control.")]
        public virtual Color GlowColor
        {
            get { return _glowColor; }
            set
            {
                if (_glowColor == value) return;
                _glowColor = value;
                OnGlowColorChanged(EventArgs.Empty);
            }
        }
        
        /// <summary>
        /// Gets or sets a value indicating whether the button should fade in and fade out when it's getting and loosing the focus.
        /// </summary>
        /// <value><c>true</c> if fading with changing the focus; otherwise, <c>false</c>.</value>
        [DefaultValue(false), Category("Appearance"), 
        Description("Indicates whether the button should fade in and fade out when it is getting and loosing the focus.")]
        public virtual bool FadeOnFocus
        {
            get { return _fadeOnFocus; }
            set { _fadeOnFocus = value; }
        }

        /// <summary>
        /// Gets the state of the button control.
        /// </summary>
        /// <value>The state of the button control.</value>
        [Browsable(false)]
        public PushButtonState State
        {
            get
            {
                if (!Enabled)
                {
                    return PushButtonState.Disabled;
                }
                if (IsPressed)
                {
                    return PushButtonState.Pressed;
                }
                if (_isHovered)
                {
                    return PushButtonState.Hot;
                }
                if (_isFocused || IsDefault)
                {
                    return PushButtonState.Default;
                }
                return PushButtonState.Normal;
            }
        }

        /// <summary>This property is not relevant for this class.</summary>
        /// <returns>This property is not relevant for this class.</returns>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 
        EditorBrowsable(EditorBrowsableState.Never)]
        public new FlatButtonAppearance FlatAppearance
        {
            get { return base.FlatAppearance; }
        }

        /// <summary>This property is not relevant for this class.</summary>
        /// <returns>This property is not relevant for this class.</returns>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 
        EditorBrowsable(EditorBrowsableState.Never)]
        public new FlatStyle FlatStyle
        {
            get { return base.FlatStyle; }
            set { base.FlatStyle = value; }
        }

        /// <summary>This property is not relevant for this class.</summary>
        /// <returns>This property is not relevant for this class.</returns>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 
        EditorBrowsable(EditorBrowsableState.Never)]
        public new bool UseVisualStyleBackColor
        {
            get { return base.UseVisualStyleBackColor; }
            set { base.UseVisualStyleBackColor = value; }
        }

        /// <summary>Occurs when the value of the <see cref="P:CodeLibrary.Controls.Buttons.GlassButton.GlassButton.InnerBorderColor" /> property changes.</summary>
        [Description("Event raised when the value of the InnerBorderColor property is changed."), 
        Category("Property Changed")]
        public event EventHandler InnerBorderColorChanged;

        /// <summary>Occurs when the value of the <see cref="P:CodeLibrary.Controls.Buttons.GlassButton.GlassButton.OuterBorderColor" /> property changes.</summary>
        [Description("Event raised when the value of the OuterBorderColor property is changed."), 
        Category("Property Changed")]
        public event EventHandler OuterBorderColorChanged;

        /// <summary>Occurs when the value of the <see cref="P:CodeLibrary.Controls.Buttons.GlassButton.GlassButton.ShineColor" /> property changes.</summary>
        [Description("Event raised when the value of the ShineColor property is changed."), 
        Category("Property Changed")]
        public event EventHandler ShineColorChanged;

        /// <summary>Occurs when the value of the <see cref="P:CodeLibrary.Controls.Buttons.GlassButton.GlassButton.GlowColor" /> property changes.</summary>
        [Description("Event raised when the value of the GlowColor property is changed."), 
        Category("Property Changed")]
        public event EventHandler GlowColorChanged;

        /// <summary>
        /// Occurs when the control is redrawn.
        /// </summary>
        public new event PaintEventHandler Paint;

        protected override void OnBackColorChanged(EventArgs e)
        {
            UpdateSkins(false);
            base.OnBackColorChanged(e);
        }
        
        protected virtual void OnInnerBorderColorChanged(EventArgs e)
        {
            InvalidateButton();
            if (InnerBorderColorChanged != null)
            {
                InnerBorderColorChanged(this, e);
            }
        }

        protected virtual void OnOuterBorderColorChanged(EventArgs e)
        {
            InvalidateButton();
            if (OuterBorderColorChanged != null)
            {
                OuterBorderColorChanged(this, e);
            }
        }

        protected virtual void OnShineColorChanged(EventArgs e)
        {
            InvalidateButton();
            if (ShineColorChanged != null)
            {
                ShineColorChanged(this, e);
            }
        }

        protected virtual void OnGlowColorChanged(EventArgs e)
        {
            InvalidateButton();
            if (GlowColorChanged != null)
            {
                GlowColorChanged(this, e);
            }
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            UpdateSkins(false);
            base.OnSizeChanged(e);
        }

        protected override void OnClick(EventArgs e)
        {
            _isKeyDown = _isMouseDown = false;
            base.OnClick(e);
        }

        protected override void OnEnter(EventArgs e)
        {
            _isFocused = _isFocusedByKey = true;
            base.OnEnter(e);
            if (_fadeOnFocus)
            {
                FadeIn();
            }
        }

        protected override void OnLeave(EventArgs e)
        {
            base.OnLeave(e);
            _isFocused = _isFocusedByKey = _isKeyDown = _isMouseDown = false;
            Invalidate();
            if (_fadeOnFocus)
            {
                FadeOut();
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space)
            {
                _isKeyDown = true;
                Invalidate();
            }
            base.OnKeyDown(e);
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            if (_isKeyDown && e.KeyCode == Keys.Space)
            {
                _isKeyDown = false;
                Invalidate();
            }
            base.OnKeyUp(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (!_isMouseDown && e.Button == MouseButtons.Left)
            {
                _isMouseDown = true;
                _isFocusedByKey = false;
                Invalidate();
            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (_isMouseDown)
            {
                _isMouseDown = false;
                Invalidate();
            }
            base.OnMouseUp(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.Button != MouseButtons.None)
            {
                if (!ClientRectangle.Contains(e.X, e.Y))
                {
                    if (_isHovered)
                    {
                        _isHovered = false;
                        Invalidate();
                    }
                }
                else if (!_isHovered)
                {
                    _isHovered = true;
                    Invalidate();
                }
            }
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            _isHovered = true;
            FadeIn();
            Invalidate();
            base.OnMouseEnter(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            _isHovered = false;
            if (!(FadeOnFocus && _isFocusedByKey)) FadeOut();
            Invalidate();
            base.OnMouseLeave(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            DrawButtonBackgroundFromBuffer(e.Graphics);
            DrawForegroundFromButton(e);
            DrawButtonForeground(e.Graphics);

            if (Paint != null)
            {
                Paint(this, e);
            }
        }

        private void DrawButtonBackgroundFromBuffer(Graphics graphics)
        {
            int frame;
            if (!Enabled)
            {
                frame = SKIN_DISABLED;
            }
            else if (IsPressed)
            {
                frame = SKIN_PRESSED;
            }
            else if (!IsAnimating && _currentSkin == 0)
            {
                frame = SKIN_NORMAL;
            }
            else
            {
                if (!HasAnimationFrames)
                {
                    UpdateSkins(true);
                }
                frame = SKIN_ANIMATED + _currentSkin;
            }
            if (_skins == null || _skins.Count == 0)
            {
                UpdateSkins(false);
            }
            if (_skins != null)
                graphics.DrawImage(_skins[frame], Point.Empty);
        }

        private Image CreateBackgroundFrame(bool pressed, bool hovered,
            bool animating, bool enabled, float glowOpacity)
        {
            Rectangle rect = ClientRectangle;
            if (rect.Width <= 0)
            {
                rect.Width = 1;
            }
            if (rect.Height <= 0)
            {
                rect.Height = 1;
            }
            Image img = new Bitmap(rect.Width, rect.Height);
            using (Graphics g = Graphics.FromImage(img))
            {
                g.Clear(Color.Transparent);
                ButtonUtils.DrawButtonBackground(g, rect, pressed, hovered, animating, enabled,
                    _outerBorderColor, _backColor, _glowColor, _shineColor, _innerBorderColor,
                    glowOpacity);
            }
            return img;
        }

        private void DrawButtonForeground(Graphics g)
        {
            if (Focused && ShowFocusCues)
            {
                Rectangle rect = ClientRectangle;
                rect.Inflate(-4, -4);
                ControlPaint.DrawFocusRectangle(g, rect);
            }
        }
        
        private void DrawForegroundFromButton(PaintEventArgs pevent)
        {
            if (_imageButton == null)
            {
                _imageButton = new Button {Parent = new TransparentControl()};
                _imageButton.SuspendLayout();
                _imageButton.BackColor = Color.Transparent;
                _imageButton.FlatAppearance.BorderSize = 0;
                _imageButton.FlatStyle = FlatStyle.Flat;
            }
            else
            {
                _imageButton.SuspendLayout();
            }
            _imageButton.AutoEllipsis = AutoEllipsis;
            if (Enabled)
            {
                _imageButton.ForeColor = ForeColor;
            }
            else
            {
                _imageButton.ForeColor = Color.FromArgb((3 * ForeColor.R + _backColor.R) >> 2,
                    (3 * ForeColor.G + _backColor.G) >> 2,
                    (3 * ForeColor.B + _backColor.B) >> 2);
            }
            _imageButton.Font = Font;
            _imageButton.RightToLeft = RightToLeft;
            _imageButton.Image = Image;
            if (Image != null && !Enabled)
            {
                Size size = Image.Size;
                float[][] newColorMatrix = new float[5][];
                newColorMatrix[0] = new[] { 0.2125f, 0.2125f, 0.2125f, 0f, 0f };
                newColorMatrix[1] = new[] { 0.2577f, 0.2577f, 0.2577f, 0f, 0f };
                newColorMatrix[2] = new[] { 0.0361f, 0.0361f, 0.0361f, 0f, 0f };
                float[] arr = new float[5];
                arr[3] = 1f;
                newColorMatrix[3] = arr;
                newColorMatrix[4] = new[] { 0.38f, 0.38f, 0.38f, 0f, 1f };
                System.Drawing.Imaging.ColorMatrix matrix = new System.Drawing.Imaging.ColorMatrix(newColorMatrix);
                System.Drawing.Imaging.ImageAttributes disabledImageAttr = new System.Drawing.Imaging.ImageAttributes();
                disabledImageAttr.ClearColorKey();
                disabledImageAttr.SetColorMatrix(matrix);
                _imageButton.Image = new Bitmap(Image.Width, Image.Height);
                using (Graphics gr = Graphics.FromImage(_imageButton.Image))
                {
                    gr.DrawImage(Image, new Rectangle(0, 0, size.Width, size.Height), 0, 0, size.Width, size.Height, GraphicsUnit.Pixel, disabledImageAttr);
                }
            }
            _imageButton.ImageAlign = ImageAlign;
            _imageButton.ImageIndex = ImageIndex;
            _imageButton.ImageKey = ImageKey;
            _imageButton.ImageList = ImageList;
            _imageButton.Padding = Padding;
            _imageButton.Size = Size;
            _imageButton.Text = Text;
            _imageButton.TextAlign = TextAlign;
            _imageButton.TextImageRelation = TextImageRelation;
            _imageButton.UseCompatibleTextRendering = UseCompatibleTextRendering;
            _imageButton.UseMnemonic = UseMnemonic;
            _imageButton.ResumeLayout();
            InvokePaint(_imageButton, pevent);
            if (_imageButton.Image != null && _imageButton.Image != Image)
            {
                _imageButton.Image.Dispose();
                _imageButton.Image = null;
            }
        }

        private class TransparentControl : Control
        {
            protected override void OnPaintBackground(PaintEventArgs pevent) { }
            protected override void OnPaint(PaintEventArgs e) { }
        }

        private bool HasAnimationFrames
        {
            get
            {
                return _skins != null && _skins.Count > SKIN_ANIMATED;
            }
        }

        private void InitSkins()
        {
            if (!IsHandleCreated)
            {
                return;
            }
            if (_skins == null)
            {
                _skins = new List<Image>();
            }
            _skins.Add(CreateBackgroundFrame(false, false, false, false, 0));
            _skins.Add(CreateBackgroundFrame(true, true, false, true, 0));
            _skins.Add(CreateBackgroundFrame(false, false, false, true, 0));
            for (int i = 0; i < skinsCount; i++)
            {
                _skins.Add(CreateBackgroundFrame(false, true, true, true, i / (skinsCount - 1F)));
            }
        }

        private void UpdateSkins(bool withAnimationFrames)
        {
            for (int i = 0; i < _skins.Count; i++)
            {
                Image img = _skins[i];
                using (Graphics g = Graphics.FromImage(img))
                {
                    g.Clear(Color.Transparent);
                    ButtonUtils.DrawButtonBackground(g, ClientRectangle, false, false, false, false,
                        _outerBorderColor, _backColor, _glowColor, _shineColor, _innerBorderColor, 0);
                }
            }

            _skins.Add(CreateBackgroundFrame(false, false, false, false, 0));
            _skins.Add(CreateBackgroundFrame(true, true, false, true, 0));
            _skins.Add(CreateBackgroundFrame(false, false, false, true, 0));
            if (!withAnimationFrames)
            {
                return;
            }
            for (int i = 0; i < skinsCount; i++)
            {
                _skins.Add(CreateBackgroundFrame(false, true, true, true, i / (skinsCount - 1F)));
            }
        }

        private bool IsAnimating
        {
            get
            {
                return _direction != 0;
            }
        }

        private void FadeIn()
        {
            _direction = 1;
            timer.Enabled = true;
        }

        private void FadeOut()
        {
            _direction = -1;
            timer.Enabled = true;
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            if (!timer.Enabled)
            {
                return;
            }
            Refresh();
            _currentSkin += _direction;
            if (_currentSkin == -1)
            {
                _currentSkin = 0;
                timer.Enabled = false;
                _direction = 0;
                return;
            }
            if (_currentSkin == skinsCount)
            {
                _currentSkin = skinsCount - 1;
                timer.Enabled = false;
                _direction = 0;
            }
        }

        private void InvalidateButton()
        {
            UpdateSkins(false);
            if (IsHandleCreated)
            {
                Invalidate();
            }
        }

        private void SetDefaultProperties()
        {
            timer.Interval = animationLength / skinsCount;
            base.BackColor = Color.Transparent;
            BackColor = Color.Black;
            ForeColor = Color.White;
            OuterBorderColor = Color.White;
            InnerBorderColor = Color.Black;
            ShineColor = Color.White;
            GlowColor = Color.FromArgb(-7488001); //unchecked((int)(0xFF8DBDFF)));
            SetStyle(
                ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw |
                ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.Opaque, false);
            UseVisualStyleBackColor = false;
        }

        private Color _backColor;
        private Color _innerBorderColor;
        private Color _outerBorderColor;
        private Color _shineColor;
        private Color _glowColor;
        private bool _fadeOnFocus;
        private bool _isHovered;
        private bool _isFocused;
        private bool _isFocusedByKey;
        private bool _isKeyDown;
        private bool _isMouseDown;
        private bool IsPressed { get { return _isKeyDown || (_isMouseDown && _isHovered); } }
        private List<Image> _skins;

        private const int SKIN_DISABLED = 0;
        private const int SKIN_PRESSED = 1;
        private const int SKIN_NORMAL = 2;
        private const int SKIN_ANIMATED = 3;

        private const int animationLength = 300;
        private const int skinsCount = 10;
        private int _currentSkin;
        private int _direction;
        private Button _imageButton;
    }
}
